#include "stdafx.h"
#include "menumetrics.h"

void CMenuMetrics::ToDrawRect( LPCRECT prcMeasure, const MARGINS *pmargins, __out LPRECT prcDraw )
{
	// Convert the measure rect to a drawing rect.
	SetRect(prcDraw,
		prcMeasure->left    + pmargins->cxLeftWidth,
		prcMeasure->top     + pmargins->cyTopHeight,
		prcMeasure->right   - pmargins->cxRightWidth,
		prcMeasure->bottom  - pmargins->cyBottomHeight);
}

void CMenuMetrics::ToMeasureSize( const SIZE *psizeDraw, const MARGINS *pmargins, __out LPSIZE psizeMeasure )
{
	psizeMeasure->cx = psizeDraw->cx + pmargins->cxLeftWidth + 
		pmargins->cxRightWidth;
	psizeMeasure->cy = psizeDraw->cy + pmargins->cyTopHeight + 
		pmargins->cyBottomHeight;
}


CMenuMetrics::~CMenuMetrics()
{
	Flush();
}

// Initialize the field members (on entry, hwnd is already set 
// correctly).
HRESULT CMenuMetrics::Initialize()
{
	HRESULT hr = E_FAIL;

	hTheme = OpenThemeData(hwndTheme, VSCLASS_MENU);
	if (hTheme)
	{
		GetThemePartSize(hTheme, NULL, MENU_POPUPCHECK, 0, NULL, 
			TS_TRUE, &sizePopupCheck);
		GetThemePartSize(hTheme, NULL, MENU_POPUPSEPARATOR, 0, NULL, 
			TS_TRUE, &sizePopupSeparator);

		GetThemeInt(hTheme, MENU_POPUPITEM, 0, TMT_BORDERSIZE, 
			&iPopupBorderSize); 
		GetThemeInt(hTheme, MENU_POPUPBACKGROUND, 0, TMT_BORDERSIZE, 
			&iPopupBgBorderSize); 

		GetThemeMargins(hTheme, NULL, MENU_POPUPCHECK, 0, 
			TMT_CONTENTMARGINS, NULL, &marPopupCheck); 
		GetThemeMargins(hTheme, NULL, MENU_POPUPCHECKBACKGROUND, 0, 
			TMT_CONTENTMARGINS, NULL, 
			&marPopupCheckBackground); 
		GetThemeMargins(hTheme, NULL, MENU_POPUPITEM, 0, 
			TMT_CONTENTMARGINS, NULL, &marPopupItem); 

		marPopupAccelerator = marPopupItem;
		marPopupAccelerator.cxLeftWidth = 
			marPopupAccelerator.cxRightWidth = 0;

		// Popup text margins
		MARGINS margins = marPopupItem;
		margins.cxRightWidth = iPopupBorderSize;
		margins.cxLeftWidth = iPopupBgBorderSize;
		marPopupText = margins;

		cyMarCheckBackground = marPopupCheckBackground.cyTopHeight + 
			marPopupCheckBackground.cyBottomHeight;

		hr = S_OK;
	}

	return hr;
}

// Ensure ppMetrics is allocated and properly initialized.
HRESULT CMenuMetrics::EnsureInitialized(__in HWND hwnd, __deref_inout 
										CMenuMetrics **ppMetrics)
{
	CMenuMetrics *pMetrics = *ppMetrics;
	HRESULT hr = E_NOINTERFACE;

	// Find and allocate a CMenuMetrics structure for this window if 
	// one has not been passed in.
	if (pMetrics == NULL)
	{
		pMetrics = new CMenuMetrics;
		if (pMetrics)
		{
			pMetrics->hTheme = NULL;
			pMetrics->hwndTheme = hwnd;
		}
		else
		{
			hr = E_OUTOFMEMORY;
		}
	}

	// Initialize member fields.
	if (pMetrics)
	{
		if (pMetrics->hTheme == NULL)
		{
			hr = pMetrics->Initialize();
		}
		else
		{
			hr = S_OK;
		}
	}

	*ppMetrics = pMetrics;
	return hr;
}

// Release any resources allocated by the object and setup object so 
// that next EnsureInitialized() will initialize the object.
void CMenuMetrics::Flush()
{
	if (hTheme)
	{
		CloseThemeData(hTheme);
		hTheme = NULL;
	}
}

POPUPITEMSTATES CMenuMetrics::ToItemStateId(UINT uItemState)
{
	const bool      fDisabled   = ((uItemState & (ODS_INACTIVE | 
		ODS_DISABLED)) != 0);
	const bool      fHot        = ((uItemState & (ODS_HOTLIGHT | 
		ODS_SELECTED)) != 0);
	POPUPITEMSTATES iState;

	if (fDisabled)
	{
		iState = (fHot ? MPI_DISABLEDHOT : MPI_DISABLED);
	}
	else if (fHot)
	{
		iState = MPI_HOT;
	}
	else
	{
		iState= MPI_NORMAL;
	}

	return iState;
}

POPUPCHECKBACKGROUNDSTATES CMenuMetrics::ToCheckBackgroundStateId(int iStateId)
{
	POPUPCHECKBACKGROUNDSTATES iStateIdCheckBackground;

	// Determine the check background state.
	if (iStateId == MPI_DISABLED || iStateId == MPI_DISABLEDHOT)
	{
		iStateIdCheckBackground = MCB_DISABLED;
	}
	else
	{
		iStateIdCheckBackground = MCB_NORMAL;
	}

	return iStateIdCheckBackground;
}

POPUPCHECKSTATES CMenuMetrics::ToCheckStateId(UINT fType, int iStateId)
{
	POPUPCHECKSTATES iStateIdCheck;

	if (fType & MFT_RADIOCHECK)
	{
		if (iStateId == MPI_DISABLED || iStateId == MPI_DISABLEDHOT)
		{
			iStateIdCheck = MC_BULLETDISABLED;
		}
		else
		{
			iStateIdCheck = MC_BULLETNORMAL;
		}
	}
	else
	{
		if (iStateId == MPI_DISABLED || iStateId == MPI_DISABLEDHOT)
		{
			iStateIdCheck = MC_CHECKMARKDISABLED;
		}
		else
		{
			iStateIdCheck = MC_CHECKMARKNORMAL;
		}
	}

	return iStateIdCheck;
}        

void CMenuMetrics::MeasureMenuItem(__inout MENUITEM *pmi, __inout 
								   MEASUREITEMSTRUCT *pmis)
{
	int cxTotal = 0;
	int cyMax = 0;
	SIZE sizeDraw;

	ZeroMemory(pmi->pmid->rgPopupSize, sizeof(pmi->pmid->rgPopupSize));

	MENUITEMINFO *pmii = &pmi->mii;

	// Size the check rectangle.
	sizeDraw = sizePopupCheck;
	sizeDraw.cy += marPopupCheckBackground.cyTopHeight + 
		marPopupCheckBackground.cyBottomHeight;

	ToMeasureSize(&sizeDraw, &marPopupCheck, &pmi->pmid
		->rgPopupSize[POPUP_CHECK]);
	cxTotal += pmi->pmid->rgPopupSize[POPUP_CHECK].cx;

	if (pmii->fType & MFT_SEPARATOR)
	{
		// Size the separator, using the minimum width.
		sizeDraw = sizePopupCheck;
		sizeDraw.cy = sizePopupSeparator.cy;

		ToMeasureSize(&sizeDraw, &marPopupItem, &pmi->pmid
			->rgPopupSize[POPUP_SEPARATOR]);
	}
	else
	{
		// Add check background horizontal padding.
		cxTotal += marPopupCheckBackground.cxLeftWidth + 
			marPopupCheckBackground.cxRightWidth;

		if (pmii->cch)
		{
			HDC hdc = GetDC(hwndTheme);
			if (hdc)
			{
				// Size the text subitem rectangle.
				RECT rcText = { 0 };
				GetThemeTextExtent(hTheme, 
					hdc, 
					MENU_POPUPITEM, 
					0, 
					pmi->szItemText,
					pmii->cch,
					DT_LEFT | DT_SINGLELINE,
					NULL, 
					&rcText);
				sizeDraw.cx = rcText.right;
				sizeDraw.cy = rcText.bottom;

				ToMeasureSize(&sizeDraw, &marPopupText, &pmi->pmid
					->rgPopupSize[POPUP_TEXT]);
				cxTotal += pmi->pmid->rgPopupSize[POPUP_TEXT].cx;

				ReleaseDC(hwndTheme, hdc);
			}                
		}

		// Account for selection margin padding.
		cxTotal += marPopupItem.cxLeftWidth + 
			marPopupItem.cxRightWidth;             
	}

	// Calculate maximum menu item height.
	if (pmii->fType & MFT_SEPARATOR)
	{
		cyMax = pmi->pmid->rgPopupSize[POPUP_SEPARATOR].cy;
	}
	else
	{
		for (UINT i = 0; i < POPUP_MAX; i++)
		{
			cyMax = max(cyMax, pmi->pmid->rgPopupSize[i].cy);
		}
	}

	// Return the composite sizes.
	pmis->itemWidth = cxTotal;
	pmis->itemHeight = cyMax;
}

void CMenuMetrics::LayoutMenuItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis, __out DRAWITEMMETRICS *pdim)
{
	const RECT *prcItem                 = &pdis->rcItem;
	const int   cyItem                  = RectHeight(pdis->rcItem);
	const SIZE *prgsize                 = pmi->pmid->rgPopupSize;
	const bool  fIsSeparator            = (pmi->mii.fType & MFT_SEPARATOR) != 0;
	int x  = prcItem->left + marPopupItem.cxLeftWidth; // Left gutter margin
	const int y = prcItem->top;
	RECT rcMeasure;

	for (UINT i = 0; i < POPUP_MAX; i++)
	{
		if (prgsize[i].cx)
		{
			int cx;
			int cy;

			switch (i)
			{
			case POPUP_CHECK:
				// Add left padding for the check background rectangle.
				x += marPopupCheckBackground.cxLeftWidth;

				// Right-align the check/bitmap in the column.            
				cx =  prgsize[i].cx;
				cy =  prgsize[i].cy - cyMarCheckBackground;
				break;

			default:
				cx = prgsize[i].cx;
				cy = prgsize[i].cy;
				break;
			}

			// Position and vertically center the subitem.
			SetRect(&rcMeasure, x, y, x + cx, y + cy);
			if (i == POPUP_CHECK)
			{
				ToDrawRect(&rcMeasure, &marPopupCheck, &pdim->rgrc[i]);
			}
			else
			{
				ToDrawRect(&rcMeasure, &marPopupText, &pdim->rgrc[i]);
			}
			OffsetRect(&pdim->rgrc[i], 0, (cyItem - cy) / 2);

			// Add right padding for the check background rectangle.
			if (i == POPUP_CHECK)
			{
				x += marPopupCheckBackground.cxRightWidth;
			}

			// Move to the next subitem.
			x += cx;
		}
	}

	// Calculate the check background draw size.
	SIZE sizeDraw;
	sizeDraw.cx = prgsize[POPUP_CHECK].cx;
	sizeDraw.cy = prgsize[POPUP_CHECK].cy - cyMarCheckBackground;

	// Calculate the check background measure size.
	SIZE sizeMeasure;
	ToMeasureSize(&sizeDraw, &marPopupCheckBackground, &sizeMeasure);

	// Lay out the check background rectangle.
	x = prcItem->left + marPopupItem.cxLeftWidth;

	SetRect(&rcMeasure, x, y, x + sizeMeasure.cx, y + sizeMeasure.cy);
	ToDrawRect(&rcMeasure, &marPopupCheckBackground, 
		&pdim->rcCheckBackground);
	OffsetRect(&pdim->rcCheckBackground, 0, (cyItem - sizeMeasure.cy) /  
		2);

	// Lay out gutter rectangle.
	x = prcItem->left;

	sizeDraw.cx = prgsize[POPUP_CHECK].cx;
	ToMeasureSize(&sizeDraw, &marPopupCheckBackground, &sizeMeasure);

	SetRect(&pdim->rcGutter, x, y, x + marPopupItem.cxLeftWidth + 
		sizeMeasure.cx, y + cyItem);

	if (fIsSeparator)
	{
		// Lay out the separator rectangle.
		x = pdim->rcGutter.right + marPopupItem.cxLeftWidth;

		SetRect(&rcMeasure, x, y, prcItem->right - marPopupItem.cxRightWidth, y + prgsize[POPUP_SEPARATOR].cy);
		ToDrawRect(&rcMeasure, &marPopupItem, &pdim->rgrc[POPUP_SEPARATOR]);
		OffsetRect(&pdim->rgrc[POPUP_SEPARATOR], 0, (cyItem -  
			prgsize[POPUP_SEPARATOR].cy) / 2);
	}
	else
	{
		// Lay out selection rectangle.
		x = prcItem->left + marPopupItem.cxLeftWidth;

		SetRect(&pdim->rcSelection, x, y, prcItem->right -  
			marPopupItem.cxRightWidth, y + cyItem);   
	}
}
